home *** CD-ROM | disk | FTP | other *** search
/ Mac-Source 1994 July / Mac-Source_July_1994.iso / C and C++ / Entertainment / Colin’s ABC’s / C-ABC.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-02-21  |  26.5 KB  |  1,257 lines  |  [TEXT/KAHL]

  1. /*
  2.  * - Don't enable gray line in Voice menu.
  3.  * - Save voice name in preferences.
  4.  * - Don't use any fonts that don't contain all the required characters.
  5.  * (upper/lower letters, digits).  Remove from menu if chars missing.
  6.  * - Using CharWidth ('W') is too narrow for some fonts like Black Chancery
  7.  * which seem to have wide side bearings.  But f.widMax is too wide for some
  8.  * fonts like Chicago, so that characters aren't drawn as big as they really
  9.  * could be.
  10.  */
  11.  
  12. /*
  13.  * Colin's ABC's -- Application to display characters and speak any character
  14.  * that's clicked on or typed on the keyboard.
  15.  *
  16.  * Written for my two-year-old, Colin.
  17.  *
  18.  * 01 Nov 93     Paul DuBois        dubois@primate.wisc.edu
  19.  *
  20.  * 01 Nov 93 Release 1.00
  21.  * - Created.
  22.  * 03 Dec 93 Release 1.02
  23.  * - Made some changes to ControlBar library (release 1.01) that affect
  24.  * declaration of callback functions here.
  25.  * 16 Dec 93 Release 1.03
  26.  * - Moved Talk Panel menu item from Options menu to Voice menu.  Since this
  27.  * means the main window isn't necessarily always in front, the menu hook needs
  28.  * to set more of the menu items properly according to what the front window is.
  29.  * Also took the idle procedure and attached it to the main window so it only
  30.  * runs to speak characters when main window is in front.
  31.  * - Added event inspection hook that causes autoKey events to be discarded.
  32.  * This avoids the problem that occurs when the child leans on a key for a
  33.  * while -- the queue gets many instances of the same character quickly.
  34.  * - Added Flush Queue item to Options menu that flushes the queue of
  35.  * characters waiting to be spoken.
  36.  * 17 Jan 94 Release 1.05
  37.  * - Use SkelAlert() rather than CenteredAlert().  Requires TransSkel 3.07.
  38.  * - Message.[ch] stuff revised slightly.
  39.  * 25 Jan 94
  40.  * - Window can be resized, should that be desired.  Buttons and characters inside
  41.  * them resize as well.  Because of this, I no longer use pushbutton controls
  42.  * for character buttons.  Instead, I do the drawing and tracking myself.
  43.  * - Initial standard state of window fills screen, to help prevent mouse clicks
  44.  * outside the window that bring another application forward.  Initial user state
  45.  * of window is its minimum size, positioned centrally on the screen.
  46.  * 26 Jan 94
  47.  * - Added Font menu for changing button character font.
  48.  * 31 Jan 94
  49.  * - Uses Prefs file to record font name and lettercase preferences.
  50.  * 01 Feb 94
  51.  * - Record window user state in preferences.
  52.  * 02 Feb 94
  53.  * - Does't dim Voice menu items when TalkPanel window is in front.
  54.  * - Record voice name in preferences.
  55.  *
  56.  * 20 Feb 94 Release 1.07
  57.  * - Updated for TransSkel 3.11, ControlBar 1.05, TalkStuff 1.06, TalkPanel 1.05
  58.  * (all of which now use Pascal function bindings).
  59.  */
  60.  
  61. # include    <Sound.h>
  62.  
  63. # include    "TransSkel.h"
  64.  
  65. # include    "Message.h"
  66. # include    "ControlBar.h"
  67. # include    "TalkStuff.h"
  68. # include    "TalkPanel.h"
  69.  
  70. # include    "C-ABC.h"
  71. # if ENABLE_DEBUG
  72. # include    "Debug.h"
  73. # endif
  74.  
  75.  
  76. /* \325 = left single curly quote */
  77.  
  78. # define    bailString    "\pColin\325s ABC\325s"
  79.  
  80. # define    lowerDisplayHt    55
  81. # define    speakingOffset    170
  82. # define    speakingStr        "\pSpeaking:"
  83.  
  84. # define    btnHMargin        10
  85. # define    btnTopMargin    10
  86. # define    btnBottomMargin    10
  87.  
  88. # define    btnCurvature    12
  89.  
  90. # define    btnMinHSize    20
  91. # define    btnMinHGap    6
  92. # define    btnMinVSize    20
  93. # define    btnMinVGap    6
  94.  
  95. # define    gapRatio    3
  96.  
  97. # define    maxQueue    100        /* max characters that can be queued up */
  98.  
  99.  
  100. typedef enum        /* resource numbers */
  101. {
  102.     aboutAlrtRes = 1000,
  103.     authorAlrtRes,
  104.     msgeAlrtRes,
  105.     
  106.     fileMenuNum = 1000,
  107.     editMenuNum,
  108.     voiceMenuNum,
  109.     fontMenuNum,
  110.     optionsMenuNum,
  111.  
  112.     clickWindRes = 1000
  113. };
  114.  
  115.  
  116. typedef enum        /* File menu item numbers */
  117. {
  118.     quitApp = 1
  119. };
  120.  
  121.  
  122. typedef enum        /* Options menu item numbers */
  123. {
  124.     upperCaseItem = 1,
  125.     lowerCaseItem,
  126.     oSepLine1,
  127.     flushQueue
  128. };
  129.  
  130.  
  131. static void    Terminate (void);
  132. static void    Bail (StringPtr msg);
  133.  
  134. static pascal void BarSetVolume (ControlHandle bar);
  135.  
  136.  
  137. static ControlHandle    volumeBar;
  138. static short            origVolume;
  139.  
  140. /*
  141.  * Bar is positioned after it's created
  142.  */
  143.  
  144. static BarInfo    barInfo =
  145. {
  146.     &volumeBar, "\pVolume = ", 0, 0, 0, 140,
  147.     0, 7, 0, { 0, "\p" }, BarSetVolume, nil
  148. };
  149.  
  150. static WindowPtr    clickWind;
  151.  
  152. static MenuHandle    fileMenu;
  153. static MenuHandle    editMenu;
  154. static MenuHandle    voiceMenu;
  155. static MenuHandle    fontMenu;
  156. static MenuHandle    optionsMenu;
  157.  
  158. static char    queue[maxQueue];
  159. static int    queueLen = 0;
  160. static char    currentChar = '\0';
  161. static Rect    charDispRect;
  162.  
  163.  
  164. static short    btnHSize = btnMinHSize;
  165. static short    btnVSize = btnMinVSize;
  166. static short    btnHGap = btnMinHGap;
  167. static short    btnVGap = btnMinVGap;
  168.  
  169. static short    actualBtnHMargin = btnHMargin;
  170.  
  171. static short    size[] = { 9, 10, 12, 14, 18, 20, 24, 32, 36, 48, 72, 0 };
  172. static short    sizeIndex = 0;
  173.  
  174. static Prefs    prefs;
  175. static short    fontNum = -1;    /* -1 so first SetFont() does something */
  176.  
  177.  
  178. /* ------------------------------------------------------------------ */
  179.  
  180. /*
  181.  * Sound volume management
  182.  */
  183.  
  184.  
  185. /*
  186.  * Get the volume.
  187.  */
  188.  
  189. static short
  190. GetVolume (void)
  191. {
  192. short value;
  193.  
  194.     GetSoundVol (&value);
  195.     return (value);
  196. }
  197.  
  198.  
  199. /*
  200.  * Set the volume, if it the new value is different than the current.
  201.  */
  202.  
  203. static void
  204. SetVolume (short value)
  205. {
  206.     if (value != GetVolume ())
  207.         SetSoundVol (value);
  208. }
  209.  
  210.  
  211. /*
  212.  * Procedure that's called when the volume bar control value is changed.
  213.  */
  214.  
  215. static pascal void
  216. BarSetVolume (ControlHandle bar)
  217. {
  218.     SetVolume (GetCtlValue (bar));
  219. }
  220.  
  221.  
  222. /* ------------------------------------------------------------------ */
  223.  
  224. /*
  225.  * Character queue management
  226.  */
  227.  
  228.  
  229. /*
  230.  * Put a character in the queue.  Return non-zero if sucessful,
  231.  * zero if queue is full.
  232.  */
  233.  
  234. static int
  235. QueueChar (char c)
  236. {
  237.     if (queueLen >= maxQueue)
  238.         return (0);
  239.     queue[queueLen++] = c;
  240.     return (1);
  241. }
  242.  
  243.  
  244. /*
  245.  * Pull a character off the queue and return it.  Return '\0' if empty.
  246.  */
  247.  
  248. static char
  249. DequeueChar (void)
  250. {
  251. char    c;
  252.  
  253.     if (queueLen == 0)
  254.         return ('\0');
  255.     c = queue[0];
  256.     BlockMove (queue + 1, queue, --queueLen);
  257.     return (c);
  258. }
  259.  
  260.  
  261. /*
  262.  * Flush the queue of any outstanding characters.
  263.  */
  264.  
  265. static void
  266. FlushQueue (void)
  267. {
  268.     queueLen = 0;
  269. }
  270.  
  271.  
  272. /* ------------------------------------------------------------------ */
  273.  
  274. /*
  275.  * Window layout and content management
  276.  */
  277.  
  278.  
  279. static char
  280. GetCharButtonChar (short i)
  281. {
  282.     if (i < 26)            /* it's a letter */
  283.         return ((prefs.letterCase == upperCase ? 'A' : 'a') + i);
  284.     /* it's a digit */
  285.     return ('0' + (i - 26));
  286. }
  287.  
  288.  
  289. static void
  290. GetCharButtonRect (short i, Rect *r)
  291. {
  292. short    h, v;
  293.  
  294.     if (i < 26)            /* it's a letter */
  295.     {
  296.         h = (i % 13) * (btnHSize + btnHGap);
  297.         v = (i / 13) * (btnVSize + btnVGap);
  298.     }
  299.     else                /* it's a digit */
  300.     {
  301.         h = (i - 26) * (btnHSize + btnHGap) + (13 - 10) * (btnHSize + btnHGap) / 2;
  302.         v = 2 * (btnVSize + btnVGap);
  303.     }
  304.     SetRect (r, 0, 0, btnHSize, btnVSize);
  305.     OffsetRect (r, actualBtnHMargin + h, btnTopMargin + v);
  306. }    
  307.  
  308.  
  309. /*
  310.  * Calculation of rect within which currently spoken character is displayed
  311.  */
  312.  
  313. static void
  314. CalcCharDispRect (void)
  315. {
  316. Rect    r;
  317.  
  318.     r = clickWind->portRect;
  319.     SetRect (&charDispRect, 0, 0, 16, 16);
  320.     OffsetRect (&charDispRect,
  321.                 r.left + speakingOffset + StringWidth (speakingStr) + 5,
  322.                 r.bottom - 32);
  323. }
  324.  
  325.  
  326. /*
  327.  * Invalidate character button portion of display to force redraw
  328.  */
  329.  
  330. static void
  331. InvalButtonArea (void)
  332. {
  333. Rect    r;
  334.  
  335.     r = clickWind->portRect;
  336.     r.bottom -= lowerDisplayHt;
  337.     InvalRect (&r);
  338. }
  339.  
  340.  
  341. /*
  342.  * Calculate size and spacing of character buttons
  343.  */
  344.  
  345. static Boolean
  346. TestLayout (short max, short btnSize, short gapSize, short nButtons)
  347. {
  348.     return ((btnSize + gapSize) * nButtons - gapSize <= max);
  349. }
  350.  
  351.  
  352. static void
  353. CalcButtonLayout (void)
  354. {
  355. Rect    r;
  356. short    maxHSize;
  357. short    maxVSize;
  358. short    steps;
  359. short    loop;
  360.  
  361.     r = clickWind->portRect;
  362.     maxHSize = (r.right - r.left) - 2 * btnHMargin;
  363.     maxVSize = (r.bottom - r.top) - btnTopMargin - btnBottomMargin - lowerDisplayHt;
  364.  
  365.     /*
  366.      * Start with minimum size buttons and gaps, then increase size to max
  367.      * that will fit.  Increase the gap every gapRatio steps, increase button
  368.      * size every step.
  369.      */
  370.  
  371.     btnHSize = btnMinHSize;
  372.     btnVSize = btnMinVSize;
  373.     btnHGap = btnMinHGap;
  374.     btnVGap = btnMinVGap;
  375.     steps = 0;
  376.     loop = 1;
  377.     while (loop)
  378.     {
  379.         /* loop as long as some increase in size takes place */
  380.  
  381.         loop = 0;
  382.         if (steps >= gapRatio)
  383.             steps = 0;
  384.         if (++steps == 1)
  385.         {
  386.             if (TestLayout (maxHSize, btnHSize, btnHGap + 1, 13))
  387.             {
  388.                 ++btnHGap;
  389.                 loop = 1;
  390.             }
  391.             if (TestLayout (maxVSize, btnVSize, btnVGap + 1, 3))
  392.             {
  393.                 ++btnVGap;
  394.                 loop = 1;
  395.             }
  396.         }
  397.         if (TestLayout (maxHSize, btnHSize + 1, btnHGap, 13))
  398.         {
  399.             ++btnHSize;
  400.             loop = 1;
  401.         }
  402.         if (TestLayout (maxVSize, btnVSize + 1, btnVGap, 3))
  403.         {
  404.             ++btnVSize;
  405.             loop = 1;
  406.         }
  407.     }
  408.     actualBtnHMargin = btnHMargin
  409.                         + (maxHSize - ((btnHSize + btnHGap) * 13 - btnHGap)) / 2;
  410. }
  411.  
  412.  
  413. static short
  414. FontCharWidth (void)
  415. {
  416. short    i, wid, maxWid;
  417.  
  418.     maxWid = 0;
  419.     for (i = 0; i < 36; i++)
  420.     {
  421.         wid = CharWidth (GetCharButtonChar (i));
  422.         if (maxWid < wid)
  423.             maxWid = wid;
  424.     }
  425.     return (maxWid);
  426. }
  427.  
  428.  
  429. /*
  430.  * Calculate biggest size for current font that will fit in the buttons
  431.  */
  432.  
  433. static void
  434. CalcFontSize (void)
  435. {
  436. FontInfo    f;
  437. Rect        r;
  438. short        ht, wid, i;
  439.  
  440.     GetCharButtonRect (0, &r);    /* get a button rect so know how big it is */
  441.     ht = r.bottom - r.top;
  442.     wid = r.right - r.left;
  443.     sizeIndex = 0;                /* size[0] always fits, but design */
  444.     for (i = 1; size[i] != 0; i++)
  445.     {
  446.         TextSize (size[i]);
  447.         GetFontInfo (&f);
  448.         if (FontCharWidth () + 4 > wid)                    /* too wide */
  449.             break;
  450.         if (f.ascent + f.descent + f.leading + 4 > ht)    /* too tall */
  451.             break;
  452.         sizeIndex = i;
  453.     }
  454.     TextSize (0);        /* restore to normal size */
  455. }
  456.  
  457.  
  458. /*
  459.  * Set the current font.
  460.  *
  461.  * GetFNum() returns 0 if the font name is the sytem font or unknown.
  462.  * That's okay -- just use system font if font isn't known.
  463.  */
  464.  
  465. static void
  466. SetFont (StringPtr fontName)
  467. {
  468. short    newFontNum;
  469.  
  470.     GetFNum (fontName, &newFontNum);
  471.     if (fontNum != newFontNum)
  472.     {
  473.         BlockMove (fontName, prefs.fontName, (long) (fontName[0] + 1));
  474.         fontNum = newFontNum;
  475.         CalcFontSize ();
  476.         InvalButtonArea ();                /* force redraw of button area */
  477.     }
  478. }
  479.  
  480.  
  481. /*
  482.  * Position the volume scroll in the lower display area
  483.  */
  484.  
  485. static void
  486. PositionVolumeBar (void)
  487. {
  488.     MoveControl (volumeBar, 10, clickWind->portRect.bottom - 30);
  489. }
  490.  
  491.  
  492. /*
  493.  * Draw grow box of dispWind in lower right hand corner
  494.  */
  495.  
  496.  
  497. static void
  498. DrawGrowBox (WindowPtr w)
  499. {
  500. RgnHandle    oldClip;
  501. Rect        r;
  502.  
  503.     r = w->portRect;
  504.     r.left = r.right - 15;        /* draw only in corner */
  505.     r.top = r.bottom - 15;
  506.     oldClip = NewRgn ();
  507.     GetClip (oldClip);
  508.     ClipRect (&r);
  509.     DrawGrowIcon (w);
  510.     SetClip (oldClip);
  511.     DisposeRgn (oldClip);
  512. }
  513.  
  514.  
  515. /*
  516.  * Mouse was clicked in a region.  Track it and return non-zero if mouse
  517.  * ended up still in region.
  518.  *
  519.  * It seems like you should just be able to create a second region that's the
  520.  * same as rgn but inset by one pixel, and invert that to avoid inverting rgn
  521.  * and re-framing it.  But that doesn't always seem to work.  Hm.
  522.  */
  523.  
  524. static Boolean
  525. TrackRgn (RgnHandle rgn)
  526. {
  527. short    i;
  528. Boolean    inRgn;
  529. Point    pt;
  530.  
  531.     InvertRgn (rgn);
  532.     FrameRgn (rgn);
  533.     inRgn = true;
  534.     while (StillDown ())
  535.     {
  536.         GetMouse (&pt);
  537.         i = PtInRgn (pt, rgn);
  538.         if ((inRgn && !i) || (!inRgn && i))        /* was in/out, now out/in */
  539.         {
  540.             InvertRgn (rgn);
  541.             FrameRgn (rgn);
  542.             inRgn = !inRgn;
  543.         }
  544.     }
  545.     if (inRgn)                                    /* mouse ended up in rgn */
  546.     {
  547.         InvertRgn (rgn);
  548.         FrameRgn (rgn);
  549.     }
  550.     return (inRgn);
  551. }
  552.  
  553.  
  554. /* ------------------------------------------------------------------ */
  555.  
  556. /*
  557.  * Window handler routines
  558.  */
  559.  
  560.  
  561. static pascal void
  562. Mouse (Point pt, long t, short mods)
  563. {
  564. RgnHandle    rgn;
  565. ControlHandle    ctrl;
  566. short    partNo;
  567. Rect    r;
  568. short    i;
  569. char    c;
  570.  
  571.     if ((partNo = FindControl (pt, clickWind, &ctrl)) != 0)
  572.     {
  573.         TrackBar (ctrl, pt, partNo);
  574.         return;
  575.     }
  576.  
  577.     /* check for click in character button */
  578.  
  579.     for (i = 0; i < 36; i++)
  580.     {
  581.         GetCharButtonRect (i, &r);
  582.         if (PtInRect (pt, &r))
  583.         {
  584.             /* convert rect to region and track it */
  585.             rgn = NewRgn ();
  586.             OpenRgn ();
  587.             FrameRoundRect (&r, btnCurvature, btnCurvature);
  588.             CloseRgn (rgn);
  589.             if (TrackRgn (rgn))
  590.                 QueueChar (GetCharButtonChar (i));
  591.             break;
  592.         }
  593.     }
  594. }
  595.  
  596.  
  597. static pascal void
  598. Key (short c, short code, short mods)
  599. {
  600.     (void) QueueChar (c);
  601. }
  602.  
  603.  
  604. /*
  605.  * Update the click window.
  606.  */
  607.  
  608. static pascal void
  609. Update (Boolean resized)
  610. {
  611. FontInfo    f;
  612. Rect    r;
  613. short    h;
  614. short    i;
  615. char    c;
  616.  
  617.     r = clickWind->portRect;
  618.  
  619.     /*
  620.      * If window was resized, recalculate layout, invalidate the entire
  621.      * window, and return.  Another update will occur as a result of the
  622.      * invalidate, at which point the actual drawing can be done.
  623.      */
  624.  
  625.     if (resized)
  626.     {
  627.         CalcButtonLayout ();
  628.         CalcFontSize ();
  629.         CalcCharDispRect ();
  630.         PositionVolumeBar ();
  631.         InvalRect (&r);
  632.         return;
  633.     }
  634.     
  635.     EraseRect (&r);
  636.  
  637.     MoveTo (0, r.bottom - lowerDisplayHt);
  638.     LineTo (r.right, r.bottom - lowerDisplayHt);
  639.  
  640.     /* draw character buttons */
  641.     
  642.     TextFont (fontNum);
  643.     TextSize (size[sizeIndex]);
  644.     GetFontInfo (&f);
  645.     for (i = 0; i < 36; i++)
  646.     {
  647.         GetCharButtonRect (i, &r);
  648.         EraseRoundRect (&r, btnCurvature, btnCurvature);
  649.         FrameRoundRect (&r, btnCurvature, btnCurvature);
  650.         c = GetCharButtonChar (i);
  651.         h = (r.left + r.right - CharWidth (c)) / 2;
  652.         MoveTo (h, (r.top + r.bottom + f.ascent) / 2 - 1);
  653.         DrawChar (c);
  654.     }
  655.     TextFont (0);
  656.     TextSize (0);
  657.  
  658.     DrawControls (clickWind);
  659.     r = (**volumeBar).contrlRect;
  660.     InsetRect (&r, -2, -2);
  661.     FrameRect (&r);
  662.     DrawBarTitle (volumeBar);
  663.     DrawBarValue (volumeBar, true);
  664.  
  665.     /* draw "character being spoken" information */
  666.  
  667.     r = clickWind->portRect;
  668.     MoveTo (r.left + speakingOffset, r.bottom - 19);
  669.     DrawString (speakingStr);
  670.     if (currentChar != '\0')
  671.     {
  672.         h = (charDispRect.left + charDispRect.right - CharWidth (currentChar)) / 2;
  673.         MoveTo (h, charDispRect.bottom - 3);
  674.         DrawChar (currentChar);
  675.     }
  676.     DrawGrowBox (clickWind);
  677. }
  678.  
  679.  
  680. /*
  681.  * When the window goes inactive, I only unhilite the volume bar, rather than
  682.  * hiding and framing it as user interface guidelines dictate.  I think an
  683.  * empty frame looks worse than an unhilited scroll.
  684.  */
  685.  
  686. static pascal void
  687. Activate (Boolean active)
  688. {
  689.     if (!active)
  690.         HiliteControl (volumeBar, dimHilite);
  691.     else
  692.     {
  693.         SetBarValue (volumeBar, GetVolume ());
  694.         HiliteControl (volumeBar, normalHilite);
  695.     }
  696.     DrawGrowBox (clickWind);
  697. }
  698.  
  699.  
  700. static pascal void
  701. Clobber (void)
  702. {
  703.     HideWindow (clickWind);
  704.     DisposeWindow (clickWind);
  705. }
  706.  
  707.  
  708. /*
  709.  * Window idle procedure.  If the current character is done being said, erase it.
  710.  * If there are more characters in the character queue, pull the first one off the
  711.  * queue and begin saying it.
  712.  */
  713.  
  714. static pascal void
  715. Idle (void)
  716. {
  717. char    buf[2];
  718. short    h;
  719.  
  720.     if (BusyTalking ())
  721.         return;
  722.     if (currentChar != '\0')
  723.     {
  724.         EraseRect (&charDispRect);
  725.         currentChar = '\0';
  726.     }
  727.     if ((currentChar = DequeueChar ()) != '\0')
  728.     {
  729.         h = (charDispRect.left + charDispRect.right - CharWidth (currentChar)) / 2;
  730.         MoveTo (h, charDispRect.bottom - 3);
  731.         DrawChar (currentChar);
  732.         buf[0] = 1;
  733.         buf[1] = currentChar;
  734.         TalkStr ((StringPtr) buf, talkAsync);
  735.     }
  736. }
  737.  
  738.  
  739. /*
  740.  * Zoom the click window.  This differs from the default zoom proc in that
  741.  * it doesn't leave a 3-pixel border around the window, and it doesn't clip
  742.  * to the grow rect.
  743.  */
  744.  
  745. static pascal void
  746. DoZoom (WindowPtr w, short zoomDir)
  747. {
  748. Rect    r, growRect;
  749.  
  750.     EraseRect (&w->portRect);
  751.     if (zoomDir == inZoomOut)    /* zooming to default state */
  752.     {
  753.         /*
  754.          * Get the usable area of the device containing most of the
  755.          * window.  (Can ignore the result because the rect is always
  756.          * correct.  Pass nil for device parameter because it's
  757.          * irrelevant.)  Then adjust rect for title bar height.
  758.          */
  759.         (void) SkelGetWindowDevice (w, nil, &r);
  760.         r.top += SkelGetWindTitleHeight (w) - 1;
  761.         (**(WStateData **)(((WindowPeek)w)->dataHandle)).stdState = r;
  762.     }
  763.     ZoomWindow (w, zoomDir, false);
  764. }
  765.  
  766.  
  767. /*
  768.  * Initialize click window
  769.  */
  770.  
  771. static void
  772. SetupWindow (void)
  773. {
  774. Rect    r, r2;
  775. short    minHt, minWid;
  776. short    titleHeight;
  777. Boolean    useDefaultUserState = false;
  778.  
  779.     if (SkelQuery (skelQHasColorQD))
  780.         clickWind = GetNewCWindow (clickWindRes, nil, (WindowPtr) -1L);
  781.     else
  782.         clickWind = GetNewWindow (clickWindRes, nil, (WindowPtr) -1L);
  783.  
  784.     titleHeight = SkelGetWindTitleHeight (clickWind);
  785.  
  786.     /*
  787.      * Move and size window so it fills entire screen.
  788.      * Also set minimum and maximum grow bounds.
  789.      */
  790.  
  791.     (void) SkelGetWindowDevice (clickWind, (GDHandle *) nil, &r);
  792.     r.top += titleHeight - 1;
  793.     MoveWindow (clickWind, r.left, r.top, false);
  794.     SizeWindow (clickWind, r.right - r.left, r.bottom - r.top, false);
  795.  
  796.     SkelWindow (clickWind,
  797.             Mouse,
  798.             Key,
  799.             Update,
  800.             Activate,
  801.             nil,
  802.             Clobber,
  803.             Idle,
  804.             true);        /* idle only when frontmost */
  805.  
  806.     minWid = 2 * btnHMargin + 13 * btnMinHSize + 12 * btnMinHGap;
  807.     minHt = btnTopMargin + btnBottomMargin
  808.             + 3 * btnMinVSize + 2 * btnMinVGap + lowerDisplayHt;
  809.  
  810.     SkelSetGrowBounds (clickWind, minWid, minHt,
  811.                         (r.right - r.left) + 1, (r.bottom - r.top) + 1);
  812.  
  813.     /*
  814.      * - Install alternate zoom proc
  815.      * - Set standard state to full screen
  816.      * - Set user state as follows:  If there is a user state specified in the
  817.      * preferences that's entirely visible, use it.  If there was no preferences
  818.      * user state (rect in preferences is empty), or the rect specified in the
  819.      * preferences file is not entirely visible on the desktop, set the user
  820.      * state to the minimum window size, centered on screen.
  821.      */
  822.  
  823.     SkelSetZoom (clickWind, DoZoom);
  824.     (**(WStateData **)(((WindowPeek)clickWind)->dataHandle)).stdState = r;
  825.  
  826.     r2 = prefs.userState;
  827.     if (EmptyRect (&r2))
  828.         useDefaultUserState = true;
  829.     else
  830.     {
  831.         /* test whether window (incl. title bar) will be visible in this position */
  832.         r2.top -= titleHeight;
  833.         useDefaultUserState = !SkelTestRectVisible (&r2);
  834.         r2.top += titleHeight;
  835.     }
  836.     if (useDefaultUserState)
  837.     {
  838.         /* use minimum window size, centered on screen */
  839.         r2 = r;
  840.         r2.right = r2.left + minWid;
  841.         r2.bottom = r2.top + minHt;
  842.         SkelPositionRect (&r, &r2, FixRatio (1, 2), FixRatio (1, 5));
  843.     }
  844.     (**(WStateData **)(((WindowPeek)clickWind)->dataHandle)).userState = r2;
  845.  
  846.     TextFont (0);    /* 0 = system font */
  847.     TextSize (0);    /* 0 = system size */
  848.  
  849.     SetFont (prefs.fontName);
  850.     CalcButtonLayout ();
  851.     CalcFontSize ();
  852.     CalcCharDispRect ();
  853.  
  854.     MakeBar (&barInfo, clickWind);
  855.     PositionVolumeBar ();
  856.     
  857.     SetBarValue (volumeBar, GetVolume ());
  858. }
  859.  
  860.  
  861. /* ------------------------------------------------------------------ */
  862.  
  863. /*
  864.  * Menu stuff
  865.  */
  866.  
  867.  
  868. /*
  869.     Handle selection of About... item from Apple menu
  870. */
  871.  
  872. static pascal void
  873. DoAbout (short item)
  874. {
  875.     if ((SkelGetModifiers () & optionKey) == 0)
  876.         (void) SkelAlert (aboutAlrtRes, SkelDlogFilter (nil, true),
  877.                                                 skelPositionOnParentDevice);
  878.     else
  879.         (void) SkelAlert (authorAlrtRes, SkelDlogFilter (nil, true),
  880.                                                 skelPositionOnParentDevice);
  881.     SkelRmveDlogFilter ();
  882. }
  883.  
  884.  
  885. static pascal void
  886. DoFileMenu (short item)
  887. {
  888. Fixed    result;
  889.  
  890.     switch (item)
  891.     {
  892.     case quitApp:
  893.         SkelStopEventLoop ();
  894.         break;
  895.     }
  896. }
  897.  
  898.  
  899. /*
  900.  * Handle Edit menu.  This is only used for DA's since the application
  901.  * does no editing.
  902.  */
  903.  
  904. static pascal void
  905. DoEditMenu (short item)
  906. {
  907.     (void) SystemEdit (item - 1);
  908. }
  909.  
  910.  
  911. static pascal void
  912. DoVoiceMenu (short item)
  913. {
  914. short    nVoices;
  915.  
  916.     GetTalkVoiceNumRange ((short *) nil, &nVoices);
  917.     if (item <= nVoices)
  918.     {
  919.         SetTalkVoiceNum (item);
  920.         TalkVoiceNumToName (item, prefs.voiceName);
  921.     }
  922.     else
  923.         TalkPanelShow ();
  924. }
  925.  
  926.  
  927. static pascal void
  928. DoFontMenu (short item)
  929. {
  930. Str255    fontName;
  931.  
  932.     GetItem (fontMenu, item, fontName);
  933.     SetFont (fontName);
  934. }
  935.  
  936.  
  937. static pascal void
  938. DoOptionsMenu (short item)
  939. {
  940. Rect    r;
  941.  
  942.     switch (item)
  943.     {
  944.     case upperCaseItem:
  945.         if (prefs.letterCase != upperCase)    /* do nothing unless there's a change */
  946.         {
  947.             prefs.letterCase = upperCase;
  948.             InvalButtonArea ();                /* force redraw of button area */
  949.         }
  950.         break;
  951.     case lowerCaseItem:
  952.         if (prefs.letterCase != lowerCase)    /* do nothing unless there's a change */
  953.         {
  954.             prefs.letterCase = lowerCase;
  955.             InvalButtonArea ();                /* force redraw of button area */
  956.         }
  957.         break;
  958.     case flushQueue:
  959.         FlushQueue ();
  960.         break;
  961.     }
  962. }
  963.  
  964.  
  965. /*
  966.  * Set enable/disable state of a set of menu items, and optionally
  967.  * check one of them.
  968.  *
  969.  * If checkedItem is non-zero, check that item.
  970.  */
  971.  
  972. static void
  973. SetMenuItems (MenuHandle m, short min, short max, short checkedItem, Boolean enable)
  974. {
  975. short    i, nItems;
  976.  
  977.     nItems = CountMItems (m);
  978.  
  979.     for (i = min; i <= max; ++i)
  980.     {
  981.         if (enable)
  982.             EnableItem (m, i);
  983.         else
  984.             DisableItem (m, i);
  985.         SetItemMark (m, i, i == checkedItem ? checkMark : noMark);
  986.     }
  987. }
  988.  
  989.  
  990. /*
  991.  * Adjust menus.
  992.  *
  993.  * Voice menu items are enabled if click window or TalkPanel window are frontmost.
  994.  * Font and Options menu items are enabled if click window is frontmost.
  995.  */
  996.  
  997. static pascal void
  998. AdjustMenus (void)
  999. {
  1000. Str255    mFontName;
  1001. Boolean    clickWindFront, talkPanelFront;
  1002. Boolean    enable;
  1003. short    i, nItems;
  1004.  
  1005.     clickWindFront = (FrontWindow () == clickWind);
  1006.     talkPanelFront = (FrontWindow () == TalkPanelWindowPtr ());
  1007.  
  1008.     enable = clickWindFront || talkPanelFront;
  1009.     
  1010.     SetMenuItems (voiceMenu, 1, CountMItems (voiceMenu), GetTalkVoiceNum (), enable);
  1011.  
  1012.     enable = clickWindFront;
  1013.  
  1014.     /* Determine current font name item */
  1015.     
  1016.     nItems = CountMItems (fontMenu);            /* # fonts in menu */
  1017.     for (i = 1; i <= nItems; ++i)
  1018.     {
  1019.         GetItem (fontMenu, i, mFontName);    /* get font name */
  1020.         if (EqualString (prefs.fontName, mFontName, false, true))
  1021.         {
  1022.             CheckItem (fontMenu, i, true);
  1023.             break;
  1024.         }
  1025.     }
  1026.     SetMenuItems (fontMenu, 1, nItems, i, enable);
  1027.  
  1028.     SetMenuItems (optionsMenu, upperCaseItem, lowerCaseItem,
  1029.                     prefs.letterCase == upperCase ? upperCaseItem : lowerCaseItem,
  1030.                     enable);
  1031.     SetMenuItems (optionsMenu, flushQueue, flushQueue, 0, enable);
  1032. }
  1033.  
  1034.  
  1035. /*
  1036.  * Initialize menus and menu handlers.
  1037.  * \325 is curly right quote.
  1038.  * \311 is the ellipsis character.
  1039.  */
  1040.  
  1041. static void
  1042. SetupMenus (void)
  1043. {
  1044.     SkelApple ("\pAbout Colin\325s ABC\325s\311", DoAbout);
  1045.  
  1046.     if ((fileMenu = GetMenu (fileMenuNum)) == (MenuHandle) nil
  1047.         || !SkelMenu (fileMenu, DoFileMenu, nil, false, false))
  1048.         Bail ("\pProblem creating File menu.");
  1049.  
  1050.     if ((editMenu = GetMenu (editMenuNum)) == (MenuHandle) nil
  1051.         || !SkelMenu (editMenu, DoEditMenu, nil, false, false))
  1052.         Bail ("\pProblem creating Edit menu.");
  1053.  
  1054.     if ((voiceMenu = GetMenu (voiceMenuNum)) == (MenuHandle) nil
  1055.         || !SkelMenu (voiceMenu, DoVoiceMenu, nil, false, false))
  1056.         Bail ("\pProblem creating Voice menu.");
  1057.     TalkFillVoiceMenu (voiceMenu);
  1058.     AppendMenu (voiceMenu, "\p(-;Talk Panel/T");
  1059.  
  1060.     if ((fontMenu = GetMenu (fontMenuNum)) == (MenuHandle) nil
  1061.         || !SkelMenu (fontMenu, DoFontMenu, nil, false, false))
  1062.         Bail ("\pProblem creating Font menu.");
  1063.     AddResMenu (fontMenu, 'FONT');
  1064.  
  1065.     if ((optionsMenu = GetMenu (optionsMenuNum)) == (MenuHandle) nil
  1066.         || !SkelMenu (optionsMenu, DoOptionsMenu, nil, false, false))
  1067.         Bail ("\pProblem creating Options menu.");
  1068.  
  1069.     DrawMenuBar ();
  1070.  
  1071.     SkelSetMenuHook (AdjustMenus);
  1072. }
  1073.  
  1074.  
  1075. /* ------------------------------------------------------------------ */
  1076.  
  1077. /*
  1078.  * Event hook, idle processing, and suspend/resume stuff
  1079.  */
  1080.  
  1081.  
  1082. /*
  1083.  * Event hook.  On autoKey events, tell TransSkel the event was handled so that
  1084.  * it gets ignored.  This helps avoid filling up the character queue when someone
  1085.  * leans on a key.
  1086.  */
  1087.  
  1088. static pascal Boolean
  1089. EventHook (EventRecord *event)
  1090. {
  1091.     return (event->what == autoKey);
  1092. }
  1093.  
  1094.  
  1095. /*
  1096.  * Procedure to handle suspend/resume.  This application provides its
  1097.  * own local volume control, but that volume may not be appropriate for
  1098.  * other applications.
  1099.  *
  1100.  * On a suspend, save the local volume and reset volume to global value.
  1101.  *
  1102.  * On a resume, restore the local volume UNLESS the global volume has
  1103.  * changed during the suspend.  If it has, assume the user wants it to
  1104.  * apply locally, too.
  1105.  *
  1106.  * Handle suspend and resume events by activating/deactivating the
  1107.  * front window properly.  Always deactivate on suspend.  On resume,
  1108.  * only activate if there's not a mouse click for a window other than
  1109.  * the frontmost window in the event queue.  If there is, it indicates
  1110.  * the user brought the application forward by clicking in a window
  1111.  * other than the frontmost one.  Since that other window will soon
  1112.  * be activated, there's no point in activating the frontmost one.
  1113.  *
  1114.  * On suspend, don't bother setting the volume bar; it's going to be unhilited
  1115.  * anyway when the deactivate occurs.  On resume, don't set bar either; the
  1116.  * activate procedure will resync the bar to the current volume and redraw as
  1117.  * necessary.
  1118.  */
  1119.  
  1120. static pascal void
  1121. MySuspendResume (Boolean inForeground)
  1122. {
  1123. WindowPtr    w = FrontWindow ();
  1124. EventRecord    event;
  1125. GrafPtr        eventPort;
  1126. Boolean        doActivate = true;
  1127. short    newVolume;
  1128. static short    localVolume;
  1129.  
  1130.     if (!inForeground)                /* we're being suspended */
  1131.     {
  1132.         StopTalking ();
  1133.         localVolume = GetVolume ();
  1134.         SetVolume (origVolume);
  1135.     }
  1136.     else                            /* resume is imminent */
  1137.     {
  1138.         newVolume = GetVolume ();
  1139.         if (newVolume != origVolume)    /* user changed volume during suspend */
  1140.             origVolume = newVolume;
  1141.         else                            /* global volume unchanged; restore local */
  1142.             newVolume = localVolume;
  1143.         SetVolume (newVolume);
  1144.  
  1145.         if (EventAvail (mDownMask, &event))
  1146.         {
  1147.             (void) FindWindow (event.where, &eventPort);
  1148.             if (eventPort != w)
  1149.                 doActivate = false;
  1150.         }
  1151.     }
  1152.     if (doActivate)
  1153.         SkelActivate (w, inForeground);
  1154. }
  1155.  
  1156.  
  1157. /* ------------------------------------------------------------------ */
  1158.  
  1159.  
  1160. /*
  1161.  * Main program.
  1162.  */
  1163.  
  1164. int
  1165. main (void)
  1166. {
  1167.      SkelInit ((SkelInitParamsPtr) nil);
  1168.  
  1169. # if ENABLE_DEBUG
  1170.     SetupDebugWindow ();
  1171.     ShowDebugWindow ();
  1172. # endif
  1173.  
  1174.     /*
  1175.      * Get global volume and bump it up locally if it's low
  1176.      * (speech is harder to hear than beeps).
  1177.      */
  1178.  
  1179.      origVolume = GetVolume ();        /* get original volume */
  1180.      if (origVolume < 3)
  1181.          SetVolume (3);
  1182.  
  1183.     SetMessageAlertNum (msgeAlrtRes);
  1184.     if (!TalkInit ())
  1185.         Bail ("\pSpeech Manager was not found or could not be initialized.");
  1186.     if (!TalkPanelInit (100, 150))
  1187.         Bail ("\pTalk Panel could not be initialized.");
  1188.  
  1189.     /*
  1190.      * Initialize the preferences structure, then try to read the
  1191.      * preferences file to override the defaults.
  1192.      *
  1193.      * The default font name is the name of the system font.  Don't
  1194.      * assume "Chicago"; instead, get the actual name by mapping
  1195.      * systemFont onto its name.  Default userState is initialized to
  1196.      * an empty rectangle.  If the prefs file doesn't override this,
  1197.      * the window initialization code will detect the empty rect and
  1198.      * set the user state to the default user state.
  1199.      */
  1200.  
  1201.     prefs.version = prefsVersion;
  1202.     TalkVoiceNumToName (1, prefs.voiceName);
  1203.     GetFontName (systemFont, prefs.fontName);
  1204.     prefs.letterCase = upperCase;
  1205.     SetRect (&prefs.userState, 0, 0, 0, 0);
  1206.  
  1207.     ReadPreferences (&prefs);
  1208.     SetTalkVoiceNum (TalkVoiceNameToNum (prefs.voiceName));
  1209.  
  1210.     SetupMenus ();
  1211.  
  1212.     SetupWindow ();
  1213.     ShowWindow (clickWind);
  1214.     SkelDoUpdates ();
  1215.  
  1216.     SkelSetEventHook (EventHook);
  1217.     SkelSetMenuHook (AdjustMenus);
  1218.     SkelSetSuspendResume (MySuspendResume);
  1219.  
  1220.     SkelEventLoop ();
  1221.  
  1222.     Terminate ();
  1223. }
  1224.  
  1225.  
  1226. /*
  1227.  * Termination routine
  1228.  */
  1229.  
  1230. static void
  1231. Terminate (void)
  1232. {
  1233.  
  1234.     /* save final window user state in prefences */
  1235.  
  1236.     prefs.userState = (**(WStateData **)(((WindowPeek)clickWind)->dataHandle)).userState;
  1237.     WritePreferences (&prefs);
  1238.  
  1239. # if ENABLE_DEBUG
  1240.     SkelPause (120L);
  1241. # endif
  1242.  
  1243.     SetVolume (origVolume);            /* restore original volume */
  1244.     TalkPanelCleanup ();
  1245.     TalkCleanup ();
  1246.     SkelCleanup ();
  1247.     ExitToShell ();
  1248. }
  1249.  
  1250.  
  1251. static void
  1252. Bail (StringPtr msg)
  1253. {
  1254.     Message4 (bailString, "\p: ", msg, "\p  Bailing out.");
  1255.     Terminate ();
  1256. }
  1257.